FlowViz

_

declaration
_

var _ = require('lodash');

var FlowNode = require('../modules/FlowNode');
var FlowEdge = require('../modules/FlowEdge');

var fv = null;
var clickTimeout = null;

module.exports = Interactions;
module.exports.Types = [
    'Node',
    'Drag',
    'EdgeRegion',
    'NewEdge',
    'CreateEdge'
];

function Interactions(flowviz) {
    fv = flowviz;

    this._interactions = [];
    this._supers = [];

    // Defaults
    this.SetDefault("Node", "click", SelectOrRemoveNode);
    this.SetDefault("Node", "dblclick", StartConn);
    this.SetDefault("Drag", "drag", MoveNode);
    this.SetDefault("EdgeRegion", "click", SelectOrRemoveEdge);
    //this.SetDefault("EdgeRegion", "dblclick", ChangeEdgeDirection);
    this.SetDefault("NewEdge", "mousemove", UpdateNewEdge);
    this.SetDefault("NewEdge", "click", CancelEdge);
    this.SetDefault("CreateEdge", "click", FinishEdge);

    // User overrides
    var that = this;
    _.forEach(fv.Config.Interactions, function(interaction) {
       // TODO: do something to set user defined interactions
        console.log("adding interaction");
        that.SetInteraction(interaction.name, interaction.event, interaction.func);
    });
}

Interactions.prototype.SetupInteractions = function(selection, interactions) {
    _.forEach(fv.Interactions._interactions, function (int) {
        if (_.includes(interactions, int.name)) {
            selection.on(int.event, int.func);
        }
    });
};

Interactions.prototype.Clear = function(selection, interactions) {
    _.forEach(fv.Interactions._interactions, function (int) {
        if (_.includes(interactions, int.name)) {
            selection.on(int.event, null);
        }
    });
};

Interactions.prototype.SetDefault = function(name, domEvt, func) {
    var index = _.findIndex(this._interactions, function(i) {
        return i.name === name && i.event === domEvt;
    });

    if(index < 0) {
        this._interactions.push({
            name: name,
            event: domEvt,
            func: func
        });

        this._supers.push({
            name: name,
            event: domEvt,
            func: func
        });

        return;
    }

    throw new Error("You can't set more than one default interaction!");
};

Interactions.prototype.RunSuper = function(name, event, thisArg) {
    var index = _.findIndex(this._supers, function(i) {
        return i.name === name && i.event === event;
    });

    this._supers[index].func.apply(thisArg, _.slice(arguments, 3));
};

Interactions.prototype.SetInteraction = function(name, domEvt, func) {

    var index = _.findIndex(this._interactions, function(i) {
        return i.name === name && i.event === domEvt;
    });

    if(index < 0) {

        this._interactions.push({
            name: name,
            event: domEvt,
            func: func
        });

    } else {
        this._interactions[index].func = func;
    }
};

Interactions.prototype.GetDomEvent = function(name) {
    var index = _.findIndex(this._interactions, function(i) {
        return i.name === name;
    });

    if(index >= 0) {
        return this._interactions[index].event;
    }

    throw new Error(name + " is not a registered interaction!");
};

Interactions.prototype.GetFunc = function(name) {
    var index = _.findIndex(this._interactions, function(i) {
        return i.name === name;
    });

    if(index >= 0) {
        return this._interactions[index].func;
    }

    throw new Error(name + " is not a registered interaction!");
};

function SelectOrRemoveNode(d, i) {
    // When there is a selected node, stop the click propagation
    if(fv.Selection.Current !== null) {
        d3.event.stopPropagation();
    }

    // Dragging shouldn't change a selection
    if (d3.event.defaultPrevented) return;

    // If ctrl is down, remove the node, otherwise, select it
    if (d3.event.ctrlKey) {
        fv.Renderer.Reinitialize();
        fv.GraphManager.RemoveNode(d);
    } else {
        d3.event.stopPropagation();
        if(clickTimeout === null) {
            clickTimeout = setTimeout(function (data, that) {
                fv.Selection.UpdateSelection(data, d3.select(that));
                clickTimeout = null;
            }, 200, d, this);
        }
    }
}

function ChangeEdgeDirection(d, i) {
    //d3.event.sourceEvent.stopPropagation();
    d.direction = (d.direction + 1) % FlowEdge.TOTAL_DIRS;
    fv.Renderer.RedrawEdges();
}

function MoveNode(d, i) {
    fv.GraphManager.MoveNode(d, d.x = d3.event.x, d.y = d3.event.y);
}

function SelectOrRemoveEdge(d, i) {
    d3.event.stopPropagation();

    if (d3.event.ctrlKey) {
        fv.Renderer.Reinitialize();
        fv.GraphManager.RemoveEdge(d);
    } else {
        fv.Selection.SelectEdge(d);
    }
}

function StartConn(d, i) {
    if(clickTimeout !== null) {
        clearTimeout(clickTimeout);
        clickTimeout = null;
    }

    fv.Selection.Clear(true);
    fv.GraphManager.StartConnection(d, d3.event.x, d3.event.y);
}

function UpdateNewEdge(evt) {
    d3.select('g.flow-node-temp')
        .attr('transform', function(d) {
            d.x = evt.offsetX;
            d.y = evt.offsetY;
            return 'translate(' + d.x + ',' + d.y + ')';
        });

    d3.select('g.flow-edge-temp')
        .each(function(d) {
            d.Update();
        })
        .select('path')
        .attr('d', function(d) {
            return fv.Renderer.DrawLine(d.getPath());
        });
}

function FinishEdge(d, i) {
    fv.GraphManager.EndConnection(d);
    fv.Renderer.RemoveTempListeners();
}

function CancelEdge() {
    fv.GraphManager.EndConnection(null);
    fv.Renderer.RemoveTempListeners();
}